home *** CD-ROM | disk | FTP | other *** search
Wrap
// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT // see COPYRIGHT for reuse legalities // /* WW3DText.m * Fashioned by Ian Wilkinson. * Inception was Sun Jul 17 16:09:45 BST 1994. * * My History: * Hacked by wave on Aug 20. * Hacked more by wave on Aug 25 to add archiving. * Hacked just a bit more by wave on Aug 25 to fix storage problems * (noted by Ian). * Hacked by iw on Sept 6 to provide correct polygon order * for RiGeneralPolygon() (see orderCounters.) * Hacked by wave on Sept 12 to add justification; bumped version number to 2 * Hacked by wave on Sept 13 to add count and objectAt: methods * Hacked by wave in early Feb to add conformance to additions to WWRenderable protocol and * and some nil and NULL setting after frees. * Wildly-controlled engineering by iw on 19 Mar 95 to introduce RiArchiveRecords into * the RIB stream. */ /* bugs: - loses its shit when handed really complicated characters; * like "WavesWorld" in OutwestHalfFull from Emigre... * - it's polygons, not a trimmed NURBS (although qrman wouldn't actually image it * right, so...) (probably the correct thing to do is have two reps; this one to * spew at qrman, a trimmed NURBS to spew at prman) * - due to the arbitariness of fonts, this object may be in way over its head * for a given piece of text. Should do some sort of sanity check... * Not sure where and when to bail, though. any ideas, ian? * - I can still get it to crash after a while, even using pretty sane stuff, * i.e., "aAbBcC" in Helvetica, a bunch of times. It usually dies in * [Storage insertElement:at:]... any ideas, ian? */ #import "WW3DText.h" #import "RIBArchiveRecord.h" #import "RIBGeneralPolygon.h" #import "WW3DTextWraps.h" #import "RIBCommandList.h" #import "RIBTransformBegin.h" #import "RIBTransformEnd.h" #import "RIBTranslate.h" #import "RIBScale.h" #import "WWSample.h" #import "WW3DAttributeState.h" // for WW_POINT definition... #import "WWEveParser.h" // for isspace() #import <ctype.h> typedef struct { int numLoops; int numCounters; int numCntlPts; } WW3DTextMetrics; @interface WW3DText(Private) - getCharPath; - copyControlPoints:(int)nCntlPts startingAt:(int)outlineIdx to:(Storage *)outlineBuffer; - orderCounters; - (Storage *)glyphBoundaries:(WW3DTextMetrics *)metrics :(int)loopIdx :(int)outlineIdx; - (int)boundaryAfter:(int)idx in:(Storage *)boundaries withNumLoops:(int)numLoops; - readCharPath:(const char *)theText usingFont:(Font *)theFont; - generateRIBCommandList; @end @implementation WW3DText(Private) static const char *FontIsProtected = "%s is protected, so its outline is entirely private."; static const int PadComment = 36; /* 'c' (of ``string'') in fontName fontSize point */ static BOOL IsCounter(float x0, float y0, float x1, float y1, float x2, float y2) { return ((x2 - x0)*(y1 - y0) - (x1 - x0)*(y2 - y0) < 0) ? YES : NO; } - getCharPath { float *charPath; int i, len, outlineLen = 0, isProtected, nLoopsInPath, *theLoops; if (!currentText || !currentFont) return self; PSWWW3DTextIsFontProtected([currentFont name], [currentFont pointSize], "l", &isProtected); if (isProtected) { NXRunAlertPanel([NXApp appName], FontIsProtected, NULL, NULL, NULL, [currentFont displayName]); return self; } nLoopsInPath = 0; len = strlen(currentText); [[textMetrics empty] setNumSlots:len]; for (i = 0; i < len; i++) { WW3DTextMetrics metrics; int nLoops, nCntlPts; PSWWW3DTextMetrics([currentFont name], [currentFont pointSize], ¤tText[i], &nLoops, &nCntlPts); metrics.numLoops = nLoops; metrics.numCntlPts = nCntlPts; [textMetrics insertElement:(void *)&metrics at:i]; outlineLen += nCntlPts; nLoopsInPath += nLoops; } theLoops = (int *)NXZoneMalloc([self zone], nLoopsInPath*sizeof(int)); if (!theLoops) return self; PSWWW3DTextLoops([currentFont name], [currentFont pointSize], currentText, nLoopsInPath, theLoops); charPath = (float *)NXZoneMalloc([self zone], outlineLen*sizeof(float)); if (!charPath) { NXZoneFree([self zone], theLoops); theLoops = NULL; return self; } PSWWW3DTextOutline([currentFont name], [currentFont pointSize], currentText, outlineLen, charPath); [[outline empty] setNumSlots:outlineLen]; for (i = 0; i < outlineLen; i++) { [outline insertElement:(void *)&charPath[i] at:i]; } [[loops empty] setNumSlots:nLoopsInPath]; for (i = 0; i < nLoopsInPath; i++) { [loops insertElement:(void *)&theLoops[i] at:i]; } NXZoneFree([self zone], charPath); charPath = NULL; NXZoneFree([self zone], theLoops); theLoops = NULL; return self; } - copyControlPoints:(int)nCntlPts startingAt:(int)outlineIdx to:(Storage *)outlineBuffer { int i; for (i = outlineIdx; i < outlineIdx+nCntlPts; i++) { [outlineBuffer addElement:[outline elementAt:i]]; } return self; } - orderCounters /* * The order of the counters from charpath and pathforall may * not be suitable for RiGeneralPolygon(). Here, we ensure * any boundary for a character appears before the holes (if any). * And yes, this solution is ugly... */ { Storage *outlineStack, *loopStack, *outlineBuffer, *loopBuffer; int loopIdx = 0, outlineIdx = 0; int len, i, j, k, nLoops, nCntlPts; if (!currentText) return self; outlineStack = [[Storage alloc] initCount:0 elementSize:sizeof(int) description:"i"]; loopStack = [[Storage alloc] initCount:0 elementSize:sizeof(int) description:"i"]; outlineBuffer = [[Storage alloc] initCount:0 elementSize:sizeof(float) description:"f"]; loopBuffer = [[Storage alloc] initCount:0 elementSize:sizeof(int) description:"i"]; len = strlen(currentText); for (i = 0; i < len; i++) { WW3DTextMetrics metrics = *(WW3DTextMetrics *)[textMetrics elementAt:i]; BOOL haveInitial; [[outlineStack empty] setNumSlots:0]; [[loopStack empty] setNumSlots:0]; nLoops = metrics.numLoops; haveInitial = NO; for (j = 0; j < nLoops; j++) { float x0, y0, x1, y1, x2, y2; nCntlPts = *(int *)[loops elementAt:loopIdx+j]; x0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-4]; y0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-3]; x1 = *(float *)[outline elementAt:outlineIdx]; y1 = *(float *)[outline elementAt:outlineIdx+1]; x2 = *(float *)[outline elementAt:outlineIdx+2]; y2 = *(float *)[outline elementAt:outlineIdx+3]; if (IsCounter(x0, y0, x1, y1, x2, y2)) { if (!haveInitial) { [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer]; [loopBuffer addElement:(void *)&nCntlPts]; if ([outlineStack count] > 0) { int count = [outlineStack count]; for (k = 0; k < count; k++) { [self copyControlPoints:*(int *)[loopStack elementAt:k] startingAt:*(int *)[outlineStack elementAt:k] to:outlineBuffer]; [loopBuffer addElement:[loopStack elementAt:k]]; } [[outlineStack empty] setNumSlots:0]; [[loopStack empty] setNumSlots:0]; } haveInitial = YES; } else { [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer]; [loopBuffer addElement:(void *)&nCntlPts]; } } else { if (!haveInitial) { [outlineStack addElement:(void *)&outlineIdx]; [loopStack addElement:(void *)&nCntlPts]; } else { [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer]; [loopBuffer addElement:(void *)&nCntlPts]; } } outlineIdx += nCntlPts; } if ([outlineStack count] > 0) { int count = [outlineStack count]; for (k = 0; k < count; k++) { [self copyControlPoints:*(int *)[loopStack elementAt:k] startingAt:*(int *)[outlineStack elementAt:k] to:outlineBuffer]; [loopBuffer addElement:[loopStack elementAt:k]]; } } loopIdx += metrics.numLoops; } [loopStack free]; loopStack = nil; [outlineStack free]; outlineStack = nil; loops = [loops free]; outline = [outline free]; loops = loopBuffer; outline = outlineBuffer; return self; } - (Storage *)glyphBoundaries:(WW3DTextMetrics *)metrics :(int)loopIdx :(int)outlineIdx { Storage *boundaries; int i = 0, nLoops, nCntlPts; boundaries = [[Storage alloc] initCount:0 elementSize:sizeof(int) description:"i"]; nLoops = metrics->numLoops; if (!nLoops) return boundaries; nCntlPts = *(int *)[loops elementAt:loopIdx]; [boundaries addElement:(void *)&i]; outlineIdx += nCntlPts; for (i = 1; i < nLoops; i++) { int nCntlPts = *(int *)[loops elementAt:loopIdx+i]; float x0, y0, x1, y1, x2, y2; x0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-4]; y0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-3]; x1 = *(float *)[outline elementAt:outlineIdx]; y1 = *(float *)[outline elementAt:outlineIdx+1]; x2 = *(float *)[outline elementAt:outlineIdx+2]; y2 = *(float *)[outline elementAt:outlineIdx+3]; if (IsCounter(x0, y0, x1, y1, x2, y2)) { [boundaries addElement:(void *)&i]; } outlineIdx += nCntlPts; } return boundaries; } - (int)boundaryAfter:(int)idx in:(Storage *)boundaries withNumLoops:(int)numLoops { int count = [boundaries count]; return (idx == count-1) ? numLoops : *(int *)[boundaries elementAt:idx+1]; } - readCharPath:(const char *)theText usingFont:(Font *)theFont { if (!theText) return self; if (currentText) NXZoneFree([self zone], currentText); currentText = NXCopyStringBufferFromZone(theText, [self zone]); currentFont = theFont; [self getCharPath]; [self orderCounters]; return self; } - generateRIBCommandList /* * Okay, so the idea here is to grovel over each character in the * string, placing it, in order, into a RIBCommandList. After we've * generated all the characters, we need to translate it so that it's * centered around the origin, and then we need to scale it so that it's * some reasonable size. As a purely ad hoc decision, I'll designate 72 * pts as 1.0, and work from there. Once we have the translation and * scaling info, we'll malloc up the appropriate RIBTransformBegin, * RIBTranslate, RIBScale, and RIBTransformEnd objects to the appropriate * places in the RIBCommandList. Hmm... do we need those to be objects? * Think about it this way: when we dump this scene out, do we spit out * a set of tokens containing the atomic rib commands that compose this object, * or do we spit out some new token corresponding to this WW3DText object? * I think the answer is pretty obviously the latter. Given that, it * doesn't even make a whole lot of sense to use a RIBCommandList to hold * our polygon info, except for the fact that it makes certain operations * easier to understand (bounding boxes, etc.). There is some performance * hit, but let's ignore that for now. * * well, the reason for a compound command like this to represent itself * as a set of objects is so that it can be motion blurred. By not making * the internals of the command opaque, we make it possible for an EveCommand * to have a chance of motion blurring this command, the same way it could * an atomic RIB command. Remember, the point of a compound command is * so that we can motion blur this command with other versions of itself. */ { int i, j, k, len, loopIdx = 0, outlineIdx = 0; RtBound polygonsBoundingBox; RtFloat polygonsCentroid[3], scaleFactor; id transformBeginCommand, transformEndCommand, translateCommand, scaleCommand; char *splitText, *curWord; splitText = NXCopyStringBufferFromZone(currentText, [self zone]); curWord = strtok(splitText, " \t\r\n\v\f"); // I should make sure there is some non-whitespace text before I waste my time... [ribCommandList empty]; len = strlen(currentText); for (i = 0; i < len; i++) { WW3DTextMetrics metrics = *(WW3DTextMetrics *)[textMetrics elementAt:i]; RIBArchiveRecord *archiveRecord; Storage *boundaries; int count; if (NXIsSpace(currentText[i])) { curWord = strtok(NULL, " \t\r\n\v\f"); } boundaries = [self glyphBoundaries:&metrics :loopIdx :outlineIdx]; count = [boundaries count]; /* * Provide an RiArchiveRecord describing the character we are about to model: * * # 'W' (of ``WavesWorld'') in Helvetica 12 point */ if (curWord && count > 0) { char *comment; int recLen; recLen = strlen(curWord) + strlen([currentFont name]) + PadComment; comment = (char *)NXZoneMalloc([self zone], sizeof(char)*recLen); sprintf(comment, " '%c' (of ``%s'') in %s %d point", currentText[i], curWord, [currentFont name], (int)[currentFont pointSize]); archiveRecord = [[RIBArchiveRecord alloc] init]; [archiveRecord setType:RI_COMMENT format:comment]; [ribCommandList addObject:archiveRecord]; NXZoneFree([self zone], comment); } for (j = 0; j < count; j++) { RIBGeneralPolygon *genP = [[RIBGeneralPolygon alloc] init]; RtInt *loopsVertices = NULL, *lvp; RtFloat *vertices = NULL, *vp; RtToken *theTokens; RtPointer *parms; int nLoops, nVertices = 0; int theBoundary, nextBoundary; char **archiveV, archiveInfo[80]; int printfTypeV[1], printfNV[1]; theBoundary = *(int *)[boundaries elementAt:j]; nextBoundary = [self boundaryAfter:j in:boundaries withNumLoops:metrics.numLoops]; nLoops = nextBoundary - theBoundary; loopsVertices = (RtInt *)NXZoneMalloc([self zone], nLoops*sizeof(RtInt)); if (!loopsVertices) { [genP free]; genP = nil; return self; } lvp = loopsVertices; for (k = theBoundary; k < nextBoundary; lvp++, k++) { *lvp = (*(int *)[loops elementAt:loopIdx+k])>>1; nVertices += *lvp; } vertices = (RtFloat *)NXZoneMalloc([self zone], nVertices*3*sizeof(RtFloat)); if (!vertices) { [genP free]; genP = nil; NXZoneFree([self zone], loopsVertices); loopsVertices = NULL; return self; } vp = vertices; for (k = theBoundary; k < nextBoundary; k++) { int s, nCntlPts = *(int *)[loops elementAt:loopIdx+k]; for (s = outlineIdx; s < nCntlPts+outlineIdx; s += 2) { *vp++ = (RtFloat)(*(float *)[outline elementAt:s]); *vp++ = (RtFloat)(*(float *)[outline elementAt:s+1]); *vp++ = (RtFloat)0.0; } outlineIdx += nCntlPts; } archiveV = (char **)NXZoneMalloc([self zone], sizeof(char *)); sprintf(archiveInfo, "[%df]", nVertices*3); *archiveV = NXCopyStringBuffer(archiveInfo); theTokens = (RtToken *)NXZoneMalloc([self zone], sizeof(RtToken)); *theTokens = RI_P; parms = (RtPointer *)NXZoneMalloc([self zone], sizeof(RtPointer)); *parms = (RtPointer)vertices; printfTypeV[0] = WW_POINT; printfNV[0] = 3*nVertices; [genP setNLoops:nLoops nVertices:loopsVertices n:1 tokens:theTokens parms:parms archiveVector:archiveV printfTypeVector:printfTypeV printfNVector:printfNV]; [genP setMyShape:myShape]; [ribCommandList addObject:genP]; } loopIdx += metrics.numLoops; [boundaries free]; boundaries = nil; } /* * okay, now we have a RIBCommandList that has all the polygons in * it. We need to get it's bounding box and calculate how we should * translate it appropriately to (left, right, center) justify it. */ if (![ribCommandList hasBoundingBox]) { // see if our text is just spaces. If it's not, this is a bug... if (currentText) // okay, we're pointing at something { int i = 0, howMany = strlen(currentText); while (isspace(currentText[i]) && i < howMany) { i++; } if (i != howMany) { NXLogError("WW3DText: although my currentText is not just spaces (it's <%s>), my polygons have no boundingBox - bailing..."); [self free]; return nil; } else { return self; // we're blank; we're done } } // okay, we've just got no text in us. (there's a song here somewhere...) polygonsBoundingBox[0] = 0; polygonsBoundingBox[1] = 0; polygonsBoundingBox[2] = 0; polygonsBoundingBox[3] = 0; polygonsBoundingBox[4] = 0; polygonsBoundingBox[5] = 0; } else { N3D_CopyBound(*([ribCommandList boundingBoxStartingAt:0 endingAt:0]), polygonsBoundingBox); } if (justification == NX_LEFTALIGNED) { polygonsCentroid[0] = 0; polygonsCentroid[1] = 0; polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); } if (justification == NX_CENTERED) { polygonsCentroid[0] = -1 * (polygonsBoundingBox[0] + ((polygonsBoundingBox[1] - polygonsBoundingBox[0])/2.0)); polygonsCentroid[1] = -1 * (polygonsBoundingBox[2] + ((polygonsBoundingBox[3] - polygonsBoundingBox[2])/2.0)); polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); } if (justification == NX_RIGHTALIGNED) { polygonsCentroid[0] = -1 * (polygonsBoundingBox[0] + ((polygonsBoundingBox[1] - polygonsBoundingBox[0]))); polygonsCentroid[1] = -1 * (polygonsBoundingBox[2] + ((polygonsBoundingBox[3] - polygonsBoundingBox[2]))); polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); } // we now want to build up a RIBTranslate object which will move that center to (0,0,0) translateCommand = [[RIBTranslate alloc] init]; [translateCommand setDX:polygonsCentroid[0] dy:polygonsCentroid[1] dz:polygonsCentroid[2]]; [translateCommand setMyShape:myShape]; // now we need to find out how big the font we're using is, so we can scale ourselves appropriately // we want 72 pts to equal 1 in Y. if (polygonsBoundingBox[3] != polygonsBoundingBox[2]) { scaleFactor = ([currentFont pointSize]/72.0) * (1./(polygonsBoundingBox[3] - polygonsBoundingBox[2])); } else { scaleFactor = 1; } scaleCommand = [[RIBScale alloc] init]; [scaleCommand setSX:scaleFactor sy:scaleFactor sz:scaleFactor]; [scaleCommand setMyShape:myShape]; transformBeginCommand = [[[RIBTransformBegin alloc] init] setMyShape:myShape]; transformEndCommand = [[[RIBTransformEnd alloc] init] setMyShape:myShape]; // okay, now let's insert the various RIBCommands in the right place in the list. // we want: {RIBTransformBegin, RIBScale, RIBTranslate, ... RIBGeneralPolygon ... RIBTransformEnd} // "wait a minute!", I hear you saying. "Don't we need to translate before we do the scale?" // Yes, but remember that RenderMan does things backwards, so this is actually correct... [ribCommandList addObject:transformEndCommand]; [ribCommandList insertObject:translateCommand at:0]; [ribCommandList insertObject:scaleCommand at:0]; [ribCommandList insertObject:transformBeginCommand at:0]; /* * Remembering to release the splitText. */ NXZoneFree([self zone], splitText); return self; } @end @implementation WW3DText static const char *InvalidJustification = "%s: %d is an invalid value for justification: only NX_LEFTALIGNED, NX_RIGHTALIGNED, NX_CENTERED are valid.\n"; + initialize { return [WW3DText setVersion:2], self; } - init { [super init]; currentText = NULL; outline = [[Storage alloc] initCount:0 elementSize:sizeof(float) description:"f"]; textMetrics = [[Storage alloc] initCount:0 elementSize:sizeof(WW3DTextMetrics) description:"{iii}"]; loops = [[Storage alloc] initCount:0 elementSize:sizeof(int) description:"i"]; ribCommandList = [[RIBCommandList alloc] init]; justification = NX_CENTERED; dirtyBoundingBox = YES; return self; } - awake { [super awake]; dirtyBoundingBox = YES; return self; } - initWithCharPath:(const char *)theText usingFont:(Font *)theFont myShape:newMyShape justification:(int)newJustification /* * This is the designated initializer for WW3DText. It returns a WW3DText * object which conforms to the WWRenderable protocol. */ { [self init]; if ((newJustification != NX_LEFTALIGNED) && (newJustification != NX_RIGHTALIGNED) && (newJustification != NX_CENTERED)) { NXLogError(InvalidJustification, [WW3DText name], newJustification); } else { justification = newJustification; } myShape = newMyShape; [self readCharPath:theText usingFont:theFont]; [self generateRIBCommandList]; return self; } - setCharPath:(const char *)theText usingFont:(Font *)theFont myShape:newMyShape justification:(int)newJustification { // in case we pop out, wannt zero these guys out ASAP... currentText = NULL; outline = nil; textMetrics = nil; loops = nil; outline = [[Storage alloc] initCount:0 elementSize:sizeof(float) description:"f"]; textMetrics = [[Storage alloc] initCount:0 elementSize:sizeof(WW3DTextMetrics) description:"{iii}"]; loops = [[Storage alloc] initCount:0 elementSize:sizeof(int) description:"i"]; dirtyBoundingBox = YES; if ((newJustification != NX_LEFTALIGNED) && (newJustification != NX_RIGHTALIGNED) && (newJustification != NX_CENTERED)) { NXLogError(InvalidJustification, [WW3DText name], newJustification); } else { justification = newJustification; } myShape = newMyShape; // we don't want to wipe out the old version... if (theText) { currentText = NXCopyStringBufferFromZone(theText, [self zone]); } else { currentText = NULL; } [self readCharPath:theText usingFont:theFont]; [self generateRIBCommandList]; return self; } - free { if (currentText) NX_FREE(currentText); [outline free]; [textMetrics free]; [loops free]; //NXLogError("WW3DText %p : freeing list %p", self, ribCommandList); [ribCommandList free]; return [super free]; } - _setRIBCommandList:newRIBCommandList { ribCommandList = newRIBCommandList; //NXLogError("set WW3DText %p's ribCommandList to %p", self, ribCommandList); return self; } - _setOutline:newOutline textMetrics:newTextMetrics loops:newLoops { outline = newOutline; textMetrics = newTextMetrics; loops = newLoops; return self; } - _setCurrentText:(const char *)newText { currentText = NXCopyStringBuffer(newText); return self; } - ribCommandList { return ribCommandList; } - copyFromZone:(NXZone *)zone { id newCopy = [super copyFromZone:zone]; [newCopy _setRIBCommandList:[ribCommandList copyFromZone:zone]]; [newCopy _setOutline:[outline copyFromZone:zone] textMetrics:[textMetrics copyFromZone:zone] loops:[loops copyFromZone:zone]]; [newCopy _setCurrentText:currentText]; return newCopy; } // for right now, we're not going to support lerping. The only useful // lerping would be with the same text changing size, anyway... - lerpWith:b by:(float)uValue { if (([self class] != [b class]) || (uValue <= 0.0)) { return self; } if (uValue >= 1.0) { return b; } return self; } - lerpSelfWith:b by:(float)uValue { if (([self class] != [b class]) || (uValue <= 0.0)) { return self; } if (uValue >= 1.0) { return b; } return self; } - (BOOL)isLerpable { return YES; } - (BOOL)hasBoundingBox { return YES; } // there is a bug in prman 3.4 that makes it muck up doing motion blur // on GeneralPolygons. Note that rendrib doesn't have this bug, and // renders GeneralPolygons in a MotionBegin/End just fine. Given that // most people are using prman 3.4, I'm going to have WW3DText say that // it's not motion blurrable for now... - (BOOL)isMotionBlurrable { return YES; } - (BOOL)isCompoundCommand { return YES; } - setBoundingBox:(RtBound *)newBoundingBox { N3D_CopyBound(*newBoundingBox, boundingBox); return self; } - calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { dirtyBoundingBox = NO; return [self setBoundingBox:[ribCommandList boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]]; } - (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStartTime endingAt:(RtFloat)intervalEndTime { if (dirtyBoundingBox) { [self calculateBoundingBoxStartingAt:intervalStartTime endingAt:intervalEndTime]; } return &boundingBox; } - (float)lastSampleIsAt { return 0.0; } - (unsigned long int)maxSampleBandwidth { return [ribCommandList maxSampleBandwidth]; } - setMyShape:shape { myShape = shape; return self; } - shape { return myShape; } - renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns { return self; } - renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns { return self; } - renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return self; } - renderMaps:(WW3DCamera *)camera { return self; } - renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return [ribCommandList renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } - renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return [ribCommandList renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } - renderSelf:(WW3DCamera *)camera { return [ribCommandList renderSelf:camera]; } - preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return [ribCommandList preRenderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } - preRenderSelf:(WW3DCamera *)camera { return [ribCommandList preRenderSelf:camera]; } - transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return [ribCommandList transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime]; } // methods to make me look like a compound command: - (int)count { return [ribCommandList count]; } - objectAt:(int)i { return [ribCommandList objectAt:i]; } // WavesWorld archiving: // writeEve:(NXStream *)stream // writeScene:(NXStream *)stream - writeEve:(NXStream *)stream atTabLevel:(int)tab { int i; for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "WW3DText {%s} %f {%s} ", [currentFont name], [currentFont pointSize], currentText); if (justification == NX_LEFTALIGNED) { NXPrintf(stream, "left;"); } else { if (justification == NX_CENTERED) { NXPrintf(stream, "center;"); } else { if (justification == NX_RIGHTALIGNED) { NXPrintf(stream, "right;"); } else { NXPrintf(stream, ";"); } } } return self; } - writeScene:(NXStream *)stream atTabLevel:(int)tab { return [self writeEve:stream atTabLevel:tab]; } - write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime { int i; for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "startShape %s; ", [[self class] name]); // need tab // need index (position in current list) NXPrintf(stream, "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", tab, index); NXPrintf(stream, " EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {"); [self writeEve:stream atTabLevel:tab]; NXPrintf(stream, "};}\n"); NXPrintf(stream, "endShape;\n"); return self; } - writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab { int i; for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "# "); [self writeEve:stream atTabLevel:tab]; NXPrintf(stream, "\n"); return [ribCommandList writeInventorAtTime:currentTime to:stream atTabLevel:tab]; } #define typeVector "@*@@@@" #define typeValues ¤tFont, ¤tText, &ribCommandList, &outline, &textMetrics, &loops - read:(NXTypedStream *)stream { int version; [super read:stream]; version = NXTypedStreamClassVersion(stream,"WW3DText"); if (version == 0) NXReadTypes(stream, "i", &version), version = 1; if (version == 1) { NXReadTypes(stream, typeVector, typeValues); NXReadArray(stream, "f", 6, boundingBox); myShape = NXReadObject(stream); justification = NX_CENTERED; } if (version == 2) { NXReadTypes(stream, typeVector, typeValues); NXReadArray(stream, "f", 6, boundingBox); myShape = NXReadObject(stream); NXReadTypes(stream, "i", &justification); } return self; } - write:(NXTypedStream *)stream { [super write:stream]; NXWriteTypes(stream, typeVector, typeValues); NXWriteArray(stream, "f", 6, boundingBox); NXWriteObjectReference(stream, myShape); NXWriteTypes(stream, "i", &justification); return self; } - (char *)currentText { return currentText; } - currentFont { return currentFont; } - (BOOL)theSameAs:otherRIBCommand { if (strcmp(currentText, [otherRIBCommand currentText])) { return NO; } if ([currentFont pointSize] != [[otherRIBCommand currentFont] pointSize]) { return NO; } if (strcmp([currentFont name], [[otherRIBCommand currentFont] name])) { return NO; } return YES; } - (BOOL)similarTo:otherRIBCommand { if ([self class] != [otherRIBCommand class]) { return NO; } return YES; } - (BOOL)isMoot { int i = 0, howMany = strlen(currentText); while (isspace(currentText[i]) && i < howMany) { i++; } if (i != howMany) // okay, I've got some text { return NO; } return YES; } - (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime { return [self isMoot]; } - (BOOL)pushesOrPopsCTM { return NO; } - (BOOL)pushesCTM { return NO; } - (BOOL)popsCTM { return NO; } // boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details - class { return [super class]; } @end